//
//
#ifndef Vector3D_h__
#define Vector3D_h__

#include "Gmath.h"
#include "Point3D.h"

namespace Geometric
{
	class Vector3D
	{
	public:
		// direction of a vector: (x,y,z)
		double m_x;
		double m_y;
		double m_z;

	public:
		Vector3D(double _x, double _y, double _z) 
			:m_x(_x), m_y(_y), m_z(_z)
		{
		}

		Vector3D(const Point3D& _start, const Point3D& _end)
			:m_x(_end.m_x - _start.m_x), m_y(_end.m_y - _start.m_y), m_z(_end.m_z - _start.m_z)
		{
		}

		Vector3D(const Vector3D& _vector) 
			:m_x(_vector.m_x), m_y(_vector.m_y), m_z(_vector.m_z)
		{
		}

		Vector3D() :m_x(0), m_y(0), m_z(0)
		{
		}

		~Vector3D()
		{
		}

	public:
		inline Vector3D& operator = (const Vector3D& _vector)
		{
			m_x = _vector.m_x;
			m_y = _vector.m_y;
			m_z = _vector.m_z;
			return (*this);
		}

	public:
		// set the value of the vector
		inline void set(double _x, double _y, double _z)
		{
			m_x = _x;
			m_y = _y;
			m_z = _z;
		}

		inline void set(const Vector3D& _vector)
		{
			set(_vector.m_x, _vector.m_y, _vector.m_z);
		}

		inline void set(const Point3D& _start, const Point3D& _end)
		{
			m_x = _end.m_x - _start.m_x;
			m_y = _end.m_y - _start.m_y;
			m_z = _end.m_z - _start.m_z;
		}

		// set the value according to the two vectors, this = _vec1 + _vec2
		inline void add(const Vector3D& _vec1, const Vector3D& _vec2)
		{
			m_x = _vec1.m_x + _vec2.m_x;
			m_y = _vec1.m_y + _vec2.m_y;
			m_z = _vec1.m_z + _vec1.m_z;
		}

		// set the value according to the two vectors, this = _vec1 - _vec2
		inline void subtract(const Vector3D& _vec1, const Vector3D& _vec2)
		{
			m_x = _vec1.m_x - _vec2.m_x;
			m_y = _vec1.m_y - _vec2.m_y;
			m_z = _vec1.m_z - _vec2.m_z;
		}

		// get dot product
		inline double getDot(double _x, double _y, double _z)
		{
			return (m_x * _x + m_y * _y + m_z * _z);
		}

		inline double getDot(const Vector3D& _vec)
		{
			return (m_x * _vec.m_x + m_y * _vec.m_y + m_z * _vec.m_z);
		}

		// get cross product
		inline void getCross(double _x, double _y, double _z, Vector3D& _vector)
		{
			_vector.m_x = m_y * _z - m_z * _y;
			_vector.m_y = m_z * _x - m_x * _z;
			_vector.m_z = m_x * _y - m_y * _x;
		}

		inline double getCross(const Vector3D& _vec, Vector3D& _vertical)
		{
			return (_vec.m_x, _vec.m_y, _vec.m_z, _vertical);
		}

		// get the length of current vector
		inline double getLength()
		{
			return Gmath::toSqrt(m_x * m_x + m_y * m_y + m_z * m_z);
		}

		// get the square of current vector
		inline double getSquare()
		{
			return (m_x * m_x + m_y * m_y + m_z * m_z);
		}

		inline void getVertical(Point& _A, Point& _B, Point& _C)
		{
			Vector3D AB, AC;
			AB.set(_A, _B);
			AC.set(_A, _C);
			getCross(AB, AC);			
		}

		// if current vector is zero-vector
		inline bool isZeroVector()
		{
			return (Gmath::isZero(m_x) && Gmath::isZero(m_y) && Gmath::isZero(m_z));
		}

		// normalize the length of the vector
		inline double normalizeLength(double _unit = 1)
		{
			double length = Gmath::toSqrt(m_x * m_x + m_y * m_y + m_z * m_z);
			if(Gmath::isZero(length) == false)
			{
				m_x *= _unit / length;
				m_y *= _unit / length;
				m_z *= _unit / length;
			}
			return length;
		}

		// if the two vector is parallel
		inline bool isParallel(double _x, double _y, double _z)
		{
			Vector3D vec;
			getCross(_x, _y, _z, vec);
			return (Gmath::isZero(vec.m_x) 
				&& Gmath::isZero(vec.m_y) 
				&& Gmath::isZero(vec.m_z));
		}

		inline bool isParallel(const Vector3D& _vec)
		{
			return isParallel(_vec.m_x, _vec.m_y, _vec.m_z);
		}

		// if the two vector is vertical
		inline bool isVertical(double _x, double _y, double _z)
		{
			double cross = getDot(_x, _y, _z);
			return Gmath::isZero(cross);
		}

		inline bool isVertical(const Vector3D& _vec)
		{
			return isVertical(_vec.m_x, _vec.m_y, _vec.m_z);
		}

		// get the angle of the two vector
		// note: the return value is less than PI
		inline double getAngle(double _x, double _y, double _z)
		{
			Vector3D vec1(_x, _y, _z);
			Vector3D vec2(m_x, m_y, m_z);

			double len1 = vec1.getLength();
			double len2 = vec2.getLength();
			double result = getDot(_x, _y, _z);
			if(Gmath::isZero(len1) == false && Gmath::isZero(len2) == false)
			{
				result = result / (len1 * len2);
				return ::acos(result);
			}
			return (-1);
		}

		inline double getAngle(const Vector3D& _vec)
		{
			return getAngle(_vec.m_x, _vec.m_y, _vec.m_z);
		}

		// if the two vector are the same direction
		inline bool isSameDirection(double _x, double _y, double _z)
		{
			if(isParallel(_x, _y, _z) 
				&& Gmath::isSameSign(m_x, _x))
			{
				return true;
			}

			return false;
		}

		inline int isSameDirection(const Vector3D& _vec)
		{
			return isSameDirection(_vec.m_x, _vec.m_y, _vec.m_z);
		}

		// if the two vector are equal
		inline bool isEqual(const Vector3D& _vec, double epsilon)
		{
			return (Gmath::isEqual(m_x, _vec.m_x, epsilon) 
				&& Gmath::isEqual(m_y, _vec.m_y, epsilon)
				&& Gmath::isEqual(m_z, _vec.m_z, epsilon));
		}
	};


}

#endif